package eval

import (
	"showen/num"
	"showen/obj"
	"testing"
)

func TestSetCar(t *testing.T) {
	env := obj.NewEnvironment()

	// Create a pair
	pair := &obj.PairObject{
		Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
		Cdr: &obj.NumberObject{Value: num.Integer{Value: 2}},
	}

	// Test set-car! with valid arguments
	newValue := &obj.NumberObject{Value: num.Integer{Value: 10}}
	args := []obj.Object{pair, newValue}
	result := SetCar(args, env)

	// Check that the function returns NULL_OBJECT
	if result.Type() != obj.NULL_OBJ {
		t.Errorf("set-car! should return NULL_OBJECT, got %v", result.Type())
	}

	// Check that the car field was updated
	if pair.Car != newValue {
		t.Errorf("set-car! should update the car field")
	}

	// Test with wrong number of arguments (too few)
	args = []obj.Object{pair}
	result = SetCar(args, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("set-car! with too few arguments should return ERROR_OBJ, got %v", result.Type())
	}

	// Test with wrong number of arguments (too many)
	args = []obj.Object{pair, newValue, newValue}
	result = SetCar(args, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("set-car! with too many arguments should return ERROR_OBJ, got %v", result.Type())
	}

	// Test with non-pair first argument
	args = []obj.Object{newValue, newValue}
	result = SetCar(args, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("set-car! with non-pair first argument should return ERROR_OBJ, got %v", result.Type())
	}
}

func TestSetCdr(t *testing.T) {
	env := obj.NewEnvironment()

	// Create a pair
	pair := &obj.PairObject{
		Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
		Cdr: &obj.NumberObject{Value: num.Integer{Value: 2}},
	}

	// Test set-cdr! with valid arguments
	newValue := &obj.NumberObject{Value: num.Integer{Value: 20}}
	args := []obj.Object{pair, newValue}
	result := SetCdr(args, env)

	// Check that the function returns NULL_OBJECT
	if result.Type() != obj.NULL_OBJ {
		t.Errorf("set-cdr! should return NULL_OBJECT, got %v", result.Type())
	}

	// Check that the cdr field was updated
	if pair.Cdr != newValue {
		t.Errorf("set-cdr! should update the cdr field")
	}

	// Test with wrong number of arguments (too few)
	args = []obj.Object{pair}
	result = SetCdr(args, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("set-cdr! with too few arguments should return ERROR_OBJ, got %v", result.Type())
	}

	// Test with wrong number of arguments (too many)
	args = []obj.Object{pair, newValue, newValue}
	result = SetCdr(args, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("set-cdr! with too many arguments should return ERROR_OBJ, got %v", result.Type())
	}

	// Test with non-pair first argument
	args = []obj.Object{newValue, newValue}
	result = SetCdr(args, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("set-cdr! with non-pair first argument should return ERROR_OBJ, got %v", result.Type())
	}
}

// Test basic CAR/CDR accessor procedures
func TestBasicCarCdrAccessors(t *testing.T) {
	env := obj.NewEnvironment()

	// Create test data: ((1 2) 3 4)
	// This represents a list with three elements:
	// - First element: (1 2) - a sublist
	// - Second element: 3
	// - Third element: 4
	innerPair := &obj.PairObject{
		Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
		Cdr: &obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 2}},
			Cdr: obj.EMPTY_LIST,
		},
	}
	testPair := &obj.PairObject{
		Car: innerPair,
		Cdr: &obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 3}},
			Cdr: &obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 4}},
				Cdr: obj.EMPTY_LIST,
			},
		},
	}

	tests := []struct {
		name     string
		fn       func([]obj.Object, *obj.Environment) obj.Object
		args     []obj.Object
		expected string
	}{
		// Basic car/cdr
		{"car", Car, []obj.Object{testPair}, "(1 2)"},
		{"cdr", Cdr, []obj.Object{testPair}, "(3 4)"},

		// caar, cadr, cdar, cddr
		{"caar", Caar, []obj.Object{testPair}, "1"},
		{"cadr", Cadr, []obj.Object{testPair}, "3"},
		{"cdar", Cdar, []obj.Object{testPair}, "(2)"},
		{"cddr", Cddr, []obj.Object{testPair}, "(4)"},
	}

	for _, tt := range tests {
		result := tt.fn(tt.args, env)

		// Check for successful result
		if result.Type() == obj.ERROR_OBJ {
			t.Errorf("%s: expected success, got error: %s", tt.name, result.Inspect())
		} else if result.Inspect() != tt.expected {
			t.Errorf("%s: expected %v, got %v", tt.name, tt.expected, result.Inspect())
		}
	}
}

// Test error cases for CAR/CDR accessor procedures
func TestCarCdrErrorCases(t *testing.T) {
	env := obj.NewEnvironment()

	// Test with wrong number of arguments
	testPair := &obj.PairObject{
		Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
		Cdr: &obj.NumberObject{Value: num.Integer{Value: 2}},
	}

	accessors := []struct {
		name string
		fn   func([]obj.Object, *obj.Environment) obj.Object
	}{
		{"caar", Caar},
		{"cadr", Cadr},
		{"cdar", Cdar},
		{"cddr", Cddr},
	}

	for _, accessor := range accessors {
		// Test with no arguments
		result := accessor.fn([]obj.Object{}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with no arguments should return ERROR_OBJ, got %v", accessor.name, result.Type())
		}

		// Test with too many arguments
		result = accessor.fn([]obj.Object{testPair, testPair}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with too many arguments should return ERROR_OBJ, got %v", accessor.name, result.Type())
		}

		// Test with non-pair argument
		result = accessor.fn([]obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with non-pair argument should return ERROR_OBJ, got %v", accessor.name, result.Type())
		}
	}
}

// Test null? and list? predicates
func TestNullAndListPredicates(t *testing.T) {
	env := obj.NewEnvironment()

	// Test null? predicate
	tests := []struct {
		name     string
		fn       func([]obj.Object, *obj.Environment) obj.Object
		args     []obj.Object
		expected bool
	}{
		// null? tests
		{"null? with EMPTY_LIST", NullP, []obj.Object{obj.EMPTY_LIST}, true},
		{"null? with NULL_OBJECT", NullP, []obj.Object{obj.NULL_OBJECT}, true},
		{"null? with pair", NullP, []obj.Object{&obj.PairObject{Car: nil, Cdr: nil}}, false},
		{"null? with number", NullP, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}}, false},

		// list? tests
		{"list? with EMPTY_LIST", ListP, []obj.Object{obj.EMPTY_LIST}, true},
		{"list? with NULL_OBJECT", ListP, []obj.Object{obj.NULL_OBJECT}, true},
		{"list? with proper list", ListP, []obj.Object{&obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
			Cdr: &obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 2}},
				Cdr: obj.EMPTY_LIST,
			},
		}}, true},
		{"list? with improper list", ListP, []obj.Object{&obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
			Cdr: &obj.NumberObject{Value: num.Integer{Value: 2}},
		}}, false},
		{"list? with pair", ListP, []obj.Object{&obj.PairObject{Car: nil, Cdr: nil}}, false},
		{"list? with number", ListP, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}}, false},
	}

	for _, tt := range tests {
		result := tt.fn(tt.args, env)

		// Check for successful result
		if result.Type() == obj.ERROR_OBJ {
			t.Errorf("%s: expected success, got error: %s", tt.name, result.Inspect())
		} else {
			booleanResult := result.(*obj.BooleanObject)
			if booleanResult.Value != tt.expected {
				t.Errorf("%s: expected %v, got %v", tt.name, tt.expected, booleanResult.Value)
			}
		}
	}
}

// Test error cases for null? and list? predicates
func TestNullAndListPredicateErrorCases(t *testing.T) {
	env := obj.NewEnvironment()

	predicates := []struct {
		name string
		fn   func([]obj.Object, *obj.Environment) obj.Object
	}{
		{"null?", NullP},
		{"list?", ListP},
	}

	for _, predicate := range predicates {
		// Test with no arguments
		result := predicate.fn([]obj.Object{}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with no arguments should return ERROR_OBJ, got %v", predicate.name, result.Type())
		}

		// Test with too many arguments
		result = predicate.fn([]obj.Object{obj.EMPTY_LIST, obj.EMPTY_LIST}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with too many arguments should return ERROR_OBJ, got %v", predicate.name, result.Type())
		}
	}
}

// Test list manipulation procedures
func TestListManipulationProcedures(t *testing.T) {
	env := obj.NewEnvironment()

	// Create test data: (1 2 3)
	testList := &obj.PairObject{
		Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
		Cdr: &obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 2}},
			Cdr: &obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 3}},
				Cdr: obj.EMPTY_LIST,
			},
		},
	}

	tests := []struct {
		name     string
		fn       func([]obj.Object, *obj.Environment) obj.Object
		args     []obj.Object
		expected string
	}{
		// length tests
		{"length of empty list", Length, []obj.Object{obj.EMPTY_LIST}, "0"},
		{"length of list", Length, []obj.Object{testList}, "3"},

		// append tests
		{"append with no arguments", Append, []obj.Object{}, "()"},
		{"append with one argument", Append, []obj.Object{testList}, "(1 2 3)"},
		{"append with two lists", Append, []obj.Object{
			&obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
				Cdr: &obj.PairObject{
					Car: &obj.NumberObject{Value: num.Integer{Value: 2}},
					Cdr: obj.EMPTY_LIST,
				},
			},
			&obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 3}},
				Cdr: &obj.PairObject{
					Car: &obj.NumberObject{Value: num.Integer{Value: 4}},
					Cdr: obj.EMPTY_LIST,
				},
			},
		}, "(1 2 3 4)"},

		// reverse tests
		{"reverse empty list", Reverse, []obj.Object{obj.EMPTY_LIST}, "()"},
		{"reverse list", Reverse, []obj.Object{testList}, "(3 2 1)"},

		// list-tail tests
		{"list-tail with k=0", ListTail, []obj.Object{testList, &obj.NumberObject{Value: num.Integer{Value: 0}}}, "(1 2 3)"},
		{"list-tail with k=1", ListTail, []obj.Object{testList, &obj.NumberObject{Value: num.Integer{Value: 1}}}, "(2 3)"},
		{"list-tail with k=2", ListTail, []obj.Object{testList, &obj.NumberObject{Value: num.Integer{Value: 2}}}, "(3)"},
		{"list-tail with k=3", ListTail, []obj.Object{testList, &obj.NumberObject{Value: num.Integer{Value: 3}}}, "()"},

		// list-ref tests
		{"list-ref with k=0", ListRef, []obj.Object{testList, &obj.NumberObject{Value: num.Integer{Value: 0}}}, "1"},
		{"list-ref with k=1", ListRef, []obj.Object{testList, &obj.NumberObject{Value: num.Integer{Value: 1}}}, "2"},
		{"list-ref with k=2", ListRef, []obj.Object{testList, &obj.NumberObject{Value: num.Integer{Value: 2}}}, "3"},
	}

	for _, tt := range tests {
		result := tt.fn(tt.args, env)

		// Check for successful result
		if result.Type() == obj.ERROR_OBJ {
			t.Errorf("%s: expected success, got error: %s", tt.name, result.Inspect())
		} else if result.Inspect() != tt.expected {
			t.Errorf("%s: expected %v, got %v", tt.name, tt.expected, result.Inspect())
		}
	}
}

// Test error cases for list manipulation procedures
func TestListManipulationErrorCases(t *testing.T) {
	env := obj.NewEnvironment()

	// Create test data: (1 2 3)
	testList := &obj.PairObject{
		Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
		Cdr: &obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 2}},
			Cdr: &obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 3}},
				Cdr: obj.EMPTY_LIST,
			},
		},
	}

	procedures := []struct {
		name string
		fn   func([]obj.Object, *obj.Environment) obj.Object
	}{
		{"length", Length},
		{"reverse", Reverse},
		{"list-tail", ListTail},
		{"list-ref", ListRef},
	}

	// Test procedures that take one argument with wrong number of arguments
	for _, proc := range procedures {
		// Test with no arguments
		result := proc.fn([]obj.Object{}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with no arguments should return ERROR_OBJ, got %v", proc.name, result.Type())
		}

		// Test with too many arguments
		result = proc.fn([]obj.Object{testList, testList, testList}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with too many arguments should return ERROR_OBJ, got %v", proc.name, result.Type())
		}
	}

	// Test append with non-list arguments
	result := Append([]obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("append with non-list argument should return ERROR_OBJ, got %v", result.Type())
	}

	// Test list-tail and list-ref with non-number second argument
	nonNumber := &obj.BooleanObject{Value: true}
	result = ListTail([]obj.Object{testList, nonNumber}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("list-tail with non-number second argument should return ERROR_OBJ, got %v", result.Type())
	}

	result = ListRef([]obj.Object{testList, nonNumber}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("list-ref with non-number second argument should return ERROR_OBJ, got %v", result.Type())
	}

	// Test list-tail and list-ref with negative index
	negativeNumber := &obj.NumberObject{Value: num.Integer{Value: -1}}
	result = ListTail([]obj.Object{testList, negativeNumber}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("list-tail with negative index should return ERROR_OBJ, got %v", result.Type())
	}

	result = ListRef([]obj.Object{testList, negativeNumber}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("list-ref with negative index should return ERROR_OBJ, got %v", result.Type())
	}

	// Test list-tail and list-ref with out of bounds index
	outOfBoundsNumber := &obj.NumberObject{Value: num.Integer{Value: 10}}
	result = ListTail([]obj.Object{testList, outOfBoundsNumber}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("list-tail with out of bounds index should return ERROR_OBJ, got %v", result.Type())
	}

	result = ListRef([]obj.Object{testList, outOfBoundsNumber}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("list-ref with out of bounds index should return ERROR_OBJ, got %v", result.Type())
	}

	// Test procedures with non-list first argument
	nonList := &obj.NumberObject{Value: num.Integer{Value: 1}}
	result = Length([]obj.Object{nonList}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("length with non-list argument should return ERROR_OBJ, got %v", result.Type())
	}

	result = Reverse([]obj.Object{nonList}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("reverse with non-list argument should return ERROR_OBJ, got %v", result.Type())
	}

	result = ListTail([]obj.Object{nonList, &obj.NumberObject{Value: num.Integer{Value: 0}}}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("list-tail with non-list first argument should return ERROR_OBJ, got %v", result.Type())
	}

	result = ListRef([]obj.Object{nonList, &obj.NumberObject{Value: num.Integer{Value: 0}}}, env)
	if result.Type() != obj.ERROR_OBJ {
		t.Errorf("list-ref with non-list first argument should return ERROR_OBJ, got %v", result.Type())
	}
}

// Test membership testing procedures
func TestMembershipProcedures(t *testing.T) {
	env := obj.NewEnvironment()

	// Create test data: (a b c) - using symbols for reliable eq? testing
	symbolA := &obj.SymbolObject{Value: "a"}
	symbolB := &obj.SymbolObject{Value: "b"}
	symbolC := &obj.SymbolObject{Value: "c"}
	symbolList := &obj.PairObject{
		Car: symbolA,
		Cdr: &obj.PairObject{
			Car: symbolB,
			Cdr: &obj.PairObject{
				Car: symbolC,
				Cdr: obj.EMPTY_LIST,
			},
		},
	}

	// Create test data for memv/member with numbers: (1 2 3)
	numberList := &obj.PairObject{
		Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
		Cdr: &obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 2}},
			Cdr: &obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 3}},
				Cdr: obj.EMPTY_LIST,
			},
		},
	}

	tests := []struct {
		name     string
		fn       func([]obj.Object, *obj.Environment) obj.Object
		args     []obj.Object
		expected bool // true if we expect a sublist, false if we expect #f
	}{
		// memq tests with symbols (eq? works reliably with symbols)
		{"memq found at beginning", Memq, []obj.Object{symbolA, symbolList}, true},
		{"memq found in middle", Memq, []obj.Object{symbolB, symbolList}, true},
		{"memq not found", Memq, []obj.Object{&obj.SymbolObject{Value: "d"}, symbolList}, false},
		{"memq with symbols - same object", Memq, []obj.Object{symbolA, symbolList}, true},

		// memv tests with numbers
		{"memv found at beginning", Memv, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}, numberList}, true},
		{"memv found in middle", Memv, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 2}}, numberList}, true},
		{"memv not found", Memv, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 4}}, numberList}, false},

		// member tests with numbers
		{"member found at beginning", Member, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}, numberList}, true},
		{"member found in middle", Member, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 2}}, numberList}, true},
		{"member not found", Member, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 4}}, numberList}, false},
	}

	for _, tt := range tests {
		result := tt.fn(tt.args, env)

		// Check result type
		if tt.expected {
			// We expect a sublist (pair object)
			if result.Type() != obj.PAIR_OBJ {
				t.Errorf("%s: expected PAIR_OBJ, got %v", tt.name, result.Type())
			}
		} else {
			// We expect #f (boolean object with false value)
			if result.Type() != obj.BOOLEAN_OBJ || result.(*obj.BooleanObject).Value != false {
				t.Errorf("%s: expected #f, got %v", tt.name, result.Inspect())
			}
		}
	}
}

// Test error cases for membership testing procedures
func TestMembershipErrorCases(t *testing.T) {
	env := obj.NewEnvironment()

	// Create test data: (1 2 3)
	testList := &obj.PairObject{
		Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
		Cdr: &obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 2}},
			Cdr: &obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 3}},
				Cdr: obj.EMPTY_LIST,
			},
		},
	}

	procedures := []struct {
		name string
		fn   func([]obj.Object, *obj.Environment) obj.Object
	}{
		{"memq", Memq},
		{"memv", Memv},
		{"member", Member},
	}

	for _, proc := range procedures {
		// Test with no arguments
		result := proc.fn([]obj.Object{}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with no arguments should return ERROR_OBJ, got %v", proc.name, result.Type())
		}

		// Test with too few arguments
		result = proc.fn([]obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with too few arguments should return ERROR_OBJ, got %v", proc.name, result.Type())
		}

		// Test with too many arguments
		result = proc.fn([]obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}, testList, &obj.NumberObject{Value: num.Integer{Value: 2}}}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with too many arguments should return ERROR_OBJ, got %v", proc.name, result.Type())
		}

		// Test with non-list second argument
		result = proc.fn([]obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}, &obj.NumberObject{Value: num.Integer{Value: 2}}}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with non-list second argument should return ERROR_OBJ, got %v", proc.name, result.Type())
		}
	}
}

// Test association list procedures
func TestAssociationListProcedures(t *testing.T) {
	env := obj.NewEnvironment()

	// Create test association list: ((a . 1) (b . 2) (c . 3))
	symbolA := &obj.SymbolObject{Value: "a"}
	symbolB := &obj.SymbolObject{Value: "b"}
	symbolC := &obj.SymbolObject{Value: "c"}
	assocList := &obj.PairObject{
		Car: &obj.PairObject{
			Car: symbolA,
			Cdr: &obj.NumberObject{Value: num.Integer{Value: 1}},
		},
		Cdr: &obj.PairObject{
			Car: &obj.PairObject{
				Car: symbolB,
				Cdr: &obj.NumberObject{Value: num.Integer{Value: 2}},
			},
			Cdr: &obj.PairObject{
				Car: &obj.PairObject{
					Car: symbolC,
					Cdr: &obj.NumberObject{Value: num.Integer{Value: 3}},
				},
				Cdr: obj.EMPTY_LIST,
			},
		},
	}

	// Create test association list with numbers: ((1 . a) (2 . b) (3 . c))
	numberAssocList := &obj.PairObject{
		Car: &obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
			Cdr: symbolA,
		},
		Cdr: &obj.PairObject{
			Car: &obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 2}},
				Cdr: symbolB,
			},
			Cdr: &obj.PairObject{
				Car: &obj.PairObject{
					Car: &obj.NumberObject{Value: num.Integer{Value: 3}},
					Cdr: symbolC,
				},
				Cdr: obj.EMPTY_LIST,
			},
		},
	}

	tests := []struct {
		name     string
		fn       func([]obj.Object, *obj.Environment) obj.Object
		args     []obj.Object
		expected bool // true if we expect a pair, false if we expect #f
	}{
		// assq tests with symbols (eq? works reliably with symbols)
		{"assq found at beginning", Assq, []obj.Object{symbolA, assocList}, true},
		{"assq found in middle", Assq, []obj.Object{symbolB, assocList}, true},
		{"assq not found", Assq, []obj.Object{&obj.SymbolObject{Value: "d"}, assocList}, false},
		{"assq with symbols - same object", Assq, []obj.Object{symbolA, assocList}, true},

		// assv tests with numbers
		{"assv found at beginning", Assv, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}, numberAssocList}, true},
		{"assv found in middle", Assv, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 2}}, numberAssocList}, true},
		{"assv not found", Assv, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 4}}, numberAssocList}, false},

		// assoc tests with numbers
		{"assoc found at beginning", Assoc, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 1}}, numberAssocList}, true},
		{"assoc found in middle", Assoc, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 2}}, numberAssocList}, true},
		{"assoc not found", Assoc, []obj.Object{&obj.NumberObject{Value: num.Integer{Value: 4}}, numberAssocList}, false},
	}

	for _, tt := range tests {
		result := tt.fn(tt.args, env)

		// Check result type
		if tt.expected {
			// We expect a pair object
			if result.Type() != obj.PAIR_OBJ {
				t.Errorf("%s: expected PAIR_OBJ, got %v", tt.name, result.Type())
			}
		} else {
			// We expect #f (boolean object with false value)
			if result.Type() != obj.BOOLEAN_OBJ || result.(*obj.BooleanObject).Value != false {
				t.Errorf("%s: expected #f, got %v", tt.name, result.Inspect())
			}
		}
	}
}

// Test error cases for association list procedures
func TestAssociationListErrorCases(t *testing.T) {
	env := obj.NewEnvironment()

	// Create test data: (1 2 3) - not an association list (elements are not pairs)
	invalidAssocList := &obj.PairObject{
		Car: &obj.NumberObject{Value: num.Integer{Value: 1}},
		Cdr: &obj.PairObject{
			Car: &obj.NumberObject{Value: num.Integer{Value: 2}},
			Cdr: &obj.PairObject{
				Car: &obj.NumberObject{Value: num.Integer{Value: 3}},
				Cdr: obj.EMPTY_LIST,
			},
		},
	}

	// Create valid association list: ((a . 1) (b . 2) (c . 3))
	symbolA := &obj.SymbolObject{Value: "a"}
	assocList := &obj.PairObject{
		Car: &obj.PairObject{
			Car: symbolA,
			Cdr: &obj.NumberObject{Value: num.Integer{Value: 1}},
		},
		Cdr: obj.EMPTY_LIST,
	}

	procedures := []struct {
		name string
		fn   func([]obj.Object, *obj.Environment) obj.Object
	}{
		{"assq", Assq},
		{"assv", Assv},
		{"assoc", Assoc},
	}

	for _, proc := range procedures {
		// Test with no arguments
		result := proc.fn([]obj.Object{}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with no arguments should return ERROR_OBJ, got %v", proc.name, result.Type())
		}

		// Test with too few arguments
		result = proc.fn([]obj.Object{symbolA}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with too few arguments should return ERROR_OBJ, got %v", proc.name, result.Type())
		}

		// Test with too many arguments
		result = proc.fn([]obj.Object{symbolA, assocList, symbolA}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with too many arguments should return ERROR_OBJ, got %v", proc.name, result.Type())
		}

		// Test with non-list second argument
		result = proc.fn([]obj.Object{symbolA, &obj.NumberObject{Value: num.Integer{Value: 1}}}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with non-list second argument should return ERROR_OBJ, got %v", proc.name, result.Type())
		}

		// Test with invalid association list (elements are not pairs)
		result = proc.fn([]obj.Object{symbolA, invalidAssocList}, env)
		if result.Type() != obj.ERROR_OBJ {
			t.Errorf("%s with invalid association list should return ERROR_OBJ, got %v", proc.name, result.Type())
		}
	}
}
